package com.elitedatai.mchtest.wayline.service.impl;

import com.dji.sdk.cloudapi.device.DeviceDomainEnum;
import com.dji.sdk.cloudapi.device.DeviceEnum;
import com.dji.sdk.cloudapi.device.DeviceSubTypeEnum;
import com.dji.sdk.cloudapi.device.DeviceTypeEnum;
import com.dji.sdk.cloudapi.wayline.GetWaylineListRequest;
import com.dji.sdk.cloudapi.wayline.GetWaylineListResponse;
import com.dji.sdk.cloudapi.wayline.WaylineTypeEnum;
import com.dji.sdk.common.Pagination;
import com.dji.sdk.common.PaginationData;
import com.elitedatai.mchtest.component.oss.model.OssConfiguration;
import com.elitedatai.mchtest.component.oss.service.impl.OssServiceContext;
import com.elitedatai.mchtest.wayline.model.dto.KmzFileProperties;
import com.elitedatai.mchtest.wayline.model.dto.WaylineFileDTO;
import com.elitedatai.mchtest.wayline.model.entity.WaylineFileEntity;
import com.elitedatai.mchtest.wayline.repository.WaylineFileRepository;
import com.elitedatai.mchtest.wayline.service.IWaylineFileService;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.util.CollectionUtils;

import static com.elitedatai.mchtest.wayline.model.dto.KmzFileProperties.WAYLINE_FILE_SUFFIX;


@Service
@Transactional
public class WaylineFileServiceImpl implements IWaylineFileService {

    @Autowired
    private WaylineFileRepository repository;

    @Autowired
    private OssServiceContext ossService;

    @Override
    public PaginationData<GetWaylineListResponse> getWaylinesByParam(String workspaceId, GetWaylineListRequest param) {
        // Paging Query
        // 创建分页请求对象
        Pageable pageable = PageRequest.of(param.getPage() - 1, param.getPageSize());

        // 构建查询条件
        Specification<WaylineFileEntity> specification = (root, query, criteriaBuilder) -> {
            List<jakarta.persistence.criteria.Predicate> predicates = new ArrayList<>();

            // workspaceId条件
            predicates.add(criteriaBuilder.equal(root.get("workspaceId"), workspaceId));

            // favorited条件
            if (param.getFavorited() != null) {
                predicates.add(criteriaBuilder.equal(root.get("favorited"), param.getFavorited()));
            }

            // templateType条件
            if (param.getTemplateType() != null && !param.getTemplateType().isEmpty()) {
                List<jakarta.persistence.criteria.Predicate> templateTypePredicates = new ArrayList<>();
                for (WaylineTypeEnum type : param.getTemplateType()) {
                    templateTypePredicates.add(criteriaBuilder.like(root.get("templateTypes"), "%" + type.getValue() + "%"));
                }
                predicates.add(criteriaBuilder.or(templateTypePredicates.toArray(new jakarta.persistence.criteria.Predicate[0])));
            }

            // payloadModelKey条件
            if (param.getPayloadModelKey() != null && !param.getPayloadModelKey().isEmpty()) {
                List<jakarta.persistence.criteria.Predicate> payloadModelKeyPredicates = new ArrayList<>();
                for (DeviceEnum type : param.getPayloadModelKey()) {
                    payloadModelKeyPredicates.add(criteriaBuilder.like(root.get("payloadModelKeys"), "%" + type.getType() + "%"));
                }
                predicates.add(criteriaBuilder.or(payloadModelKeyPredicates.toArray(new jakarta.persistence.criteria.Predicate[0])));
            }

            // droneModelKeys条件
            if (param.getDroneModelKeys() != null && !param.getDroneModelKeys().isEmpty()) {
                List<jakarta.persistence.criteria.Predicate> droneModelKeyPredicates = new ArrayList<>();
                for (DeviceEnum type : param.getDroneModelKeys()) {
                    droneModelKeyPredicates.add(criteriaBuilder.equal(root.get("droneModelKey"), type.getType()));
                }
                predicates.add(criteriaBuilder.or(droneModelKeyPredicates.toArray(new jakarta.persistence.criteria.Predicate[0])));
            }

            // name关键字搜索条件
            if (param.getKey() != null && !param.getKey().isEmpty()) {
                predicates.add(criteriaBuilder.like(root.get("name"), "%" + param.getKey() + "%"));
            }

            return criteriaBuilder.and(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
        };
        Page<WaylineFileEntity> page = repository.findAll(specification, pageable);

        // Wrap the results of a paging query into a custom paging object.
        List<GetWaylineListResponse> records = page.getContent()
                .stream()
                .map(this::entityConvertToDTO)
                .collect(Collectors.toList());

        return new PaginationData<>(records, new Pagination(page.getNumber() + 1, page.getSize(), page.getTotalElements()));
    }

    @Override
    public Optional<GetWaylineListResponse> getWaylineByWaylineId(String workspaceId, String waylineId) {
        return Optional.ofNullable(
                this.entityConvertToDTO(repository.findByWorkspaceIdAndWaylineId(workspaceId, waylineId)));
    }

    @Override
    public URL getObjectUrl(String workspaceId, String waylineId) throws SQLException {
        Optional<GetWaylineListResponse> waylineOpt = this.getWaylineByWaylineId(workspaceId, waylineId);
        if (waylineOpt.isEmpty()) {
            throw new SQLException(waylineId + " does not exist.");
        }
        return ossService.getObjectUrl(OssConfiguration.bucket, waylineOpt.get().getObjectKey());
    }

    @Override
    public Long saveWaylineFile(String workspaceId, WaylineFileDTO metadata) {
        WaylineFileEntity file = this.dtoConvertToEntity(metadata);
        file.setWaylineId(UUID.randomUUID().toString());
        file.setWorkspaceId(workspaceId);

        if (!StringUtils.hasText(file.getSign())) {
            try (InputStream object = ossService.getObject(OssConfiguration.bucket, metadata.getObjectKey())) {
                if (object.available() == 0) {
                    throw new RuntimeException("文件 " + metadata.getObjectKey() +
                            " 在该bucket中不存在[" + OssConfiguration.bucket + "].");
                }
                file.setSign(DigestUtils.md5DigestAsHex(object));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        Long insertId = repository.save(file).getId();
        return insertId > 0 ? file.getId() : insertId;
    }

    @Override
    public Boolean markFavorite(String workspaceId, List<String> waylineIds, Boolean isFavorite) {
        if (waylineIds.isEmpty()) {
            return false;
        }
        if (isFavorite == null) {
            return true;
        }
        return repository.markFavorite(workspaceId, waylineIds, isFavorite) > 0;
    }

    @Override
    public List<String> getDuplicateNames(String workspaceId, List<String> names) {
        return repository.findDuplicateNames(workspaceId, names);
    }

    @Override
    public Boolean deleteByWaylineId(String workspaceId, String waylineId) {
        Optional<GetWaylineListResponse> waylineOpt = this.getWaylineByWaylineId(workspaceId, waylineId);
        if (waylineOpt.isEmpty()) {
            return true;
        }
        GetWaylineListResponse wayline = waylineOpt.get();
        boolean isDel = repository.deleteByWorkSpaceIdAndWaylineId(workspaceId, waylineId)
                > 0;
        if (!isDel) {
            return false;
        }
        return ossService.deleteObject(OssConfiguration.bucket, wayline.getObjectKey());
    }

    @Override
    public void importKmzFile(MultipartFile file, String workspaceId, String creator) {
        Optional<WaylineFileDTO> waylineFileOpt = validKmzFile(file);
        if (waylineFileOpt.isEmpty()) {
            throw new RuntimeException("文件格式不正确");
        }

        try {
            WaylineFileDTO waylineFile = waylineFileOpt.get();
            waylineFile.setUsername(creator);

            ossService.putObject(OssConfiguration.bucket, waylineFile.getObjectKey(), file.getInputStream());
            this.saveWaylineFile(workspaceId, waylineFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Optional<WaylineFileDTO> validKmzFile(MultipartFile file) {
        // 获取上传文件的原始文件名
        String filename = file.getOriginalFilename();

        // 检查文件名是否以.kmz结尾，如果不是则抛出异常
        if (Objects.nonNull(filename) && !filename.endsWith(WAYLINE_FILE_SUFFIX)) {
            throw new RuntimeException("文件格式不正确");
        }

        // 使用try-with-resources语句确保ZipInputStream正确关闭
        try (ZipInputStream unzipFile = new ZipInputStream(file.getInputStream(), StandardCharsets.UTF_8)) {
            // 获取ZIP文件中的下一个条目
            ZipEntry nextEntry = unzipFile.getNextEntry();

            // 遍历ZIP文件中的所有条目
            while (Objects.nonNull(nextEntry)) {
                // 检查当前条目是否为template.kml文件（位于wpmz/template.kml路径下）
                boolean isWaylines = (KmzFileProperties.FILE_DIR_FIRST + "/" + KmzFileProperties.FILE_DIR_SECOND_TEMPLATE).equals(nextEntry.getName());

                // 如果不是template.kml文件，则继续处理下一个条目
                if (!isWaylines) {
                    nextEntry = unzipFile.getNextEntry();
                    continue;
                }

                // 使用SAXReader解析XML文件
                SAXReader reader = new SAXReader();
                Document document = reader.read(unzipFile);

                // 检查XML文件的编码是否为UTF-8，如果不是则抛出异常
                if (!StandardCharsets.UTF_8.name().equals(document.getXMLEncoding())) {
                    throw new RuntimeException("文件编码格式不正确");
                }

                // 从XML文档中查找无人机信息节点和负载信息节点
                Node droneNode = document.selectSingleNode("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_DRONE_INFO);
                Node payloadNode = document.selectSingleNode("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_PAYLOAD_INFO);

                // 如果无人机信息节点或负载信息节点为空，则抛出异常
                if (Objects.isNull(droneNode) || Objects.isNull(payloadNode)) {
                    throw new RuntimeException("文件格式不正确");
                }

                // 从无人机信息节点中提取无人机类型和子类型，并转换为对应的枚举值
                DeviceTypeEnum type = DeviceTypeEnum.find(Integer.parseInt(droneNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_DRONE_ENUM_VALUE)));
                DeviceSubTypeEnum subType = DeviceSubTypeEnum.find(Integer.parseInt(droneNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_DRONE_SUB_ENUM_VALUE)));

                // 从负载信息节点中提取负载类型和子类型，并转换为对应的枚举值
                DeviceTypeEnum payloadType = DeviceTypeEnum.find(Integer.parseInt(payloadNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_PAYLOAD_ENUM_VALUE)));
                DeviceSubTypeEnum payloadSubType = DeviceSubTypeEnum.find(Integer.parseInt(payloadNode.valueOf(KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_PAYLOAD_SUB_ENUM_VALUE)));

                // 从XML文档中提取模板类型
                String templateType = document.valueOf("//" + KmzFileProperties.TAG_WPML_PREFIX + KmzFileProperties.TAG_TEMPLATE_TYPE);

                // 构建并返回WaylineFileDTO对象，包含解析出的所有信息
                return Optional.of(WaylineFileDTO.builder()
                        // 获取无人机型号键值
                        .droneModelKey(DeviceEnum.find(DeviceDomainEnum.DRONE, type, subType).getDevice())
                        // 获取负载型号键值列表
                        .payloadModelKeys(List.of(DeviceEnum.find(DeviceDomainEnum.PAYLOAD, payloadType, payloadSubType).getDevice()))
                        // 设置对象键值，用于OSS存储
                        .objectKey(OssConfiguration.objectDirPrefix + File.separator + filename)
                        // 设置航线文件名称（去除.kmz后缀）
                        .name(filename.substring(0, filename.lastIndexOf(WAYLINE_FILE_SUFFIX)))
                        // 计算并设置文件的MD5签名
                        .sign(DigestUtils.md5DigestAsHex(file.getInputStream()))
                        // 设置模板类型列表
                        .templateTypes(List.of(WaylineTypeEnum.find(templateType).getValue()))
                        .build());
            }

        } catch (IOException | DocumentException e) {
            // 捕获IO异常或文档解析异常，并打印堆栈跟踪
            e.printStackTrace();
        }

        // 如果解析过程中出现任何问题，返回空的Optional对象
        return Optional.empty();
    }
    /**
     * 转换数据库实体对象到航线数据传输对象
     * @param entity
     * @return
     */
    private GetWaylineListResponse entityConvertToDTO(WaylineFileEntity entity) {
        if (entity == null) {
            return null;
        }
        return new GetWaylineListResponse()
                .setDroneModelKey(DeviceEnum.find(entity.getDroneModelKey()))
                .setFavorited(entity.getFavorited())
                .setName(entity.getName())
                .setPayloadModelKeys(entity.getPayloadModelKeys() != null ?
                        Arrays.stream(entity.getPayloadModelKeys().split(",")).map(DeviceEnum::find).collect(Collectors.toList()) : null)
                .setTemplateTypes(Arrays.stream(entity.getTemplateTypes().split(","))
                        .map(Integer::parseInt).map(WaylineTypeEnum::find)
                        .collect(Collectors.toList()))
                .setUsername(entity.getUsername())
                .setObjectKey(entity.getObjectKey())
                .setSign(entity.getSign())
                .setUpdateTime(entity.getUpdateTime())
                .setCreateTime(entity.getCreateTime())
                .setId(entity.getWaylineId());

    }

    /**
     * Convert the received wayline object into a database entity object.
     * @param file
     * @return
     */
    private WaylineFileEntity dtoConvertToEntity(WaylineFileDTO file) {
        WaylineFileEntity.WaylineFileEntityBuilder builder = WaylineFileEntity.builder();

        if (file != null) {
            builder.droneModelKey(file.getDroneModelKey())
                    .name(file.getName())
                    .username(file.getUsername())
                    .objectKey(file.getObjectKey())
                    // Separate multiple payload data with ",".
                    .payloadModelKeys(String.join(",", file.getPayloadModelKeys()))
                    .templateTypes(file.getTemplateTypes().stream()
                            .map(String::valueOf)
                            .collect(Collectors.joining(",")))
                    .favorited(file.getFavorited())
                    .sign(file.getSign())
                    .build();
        }

        return builder.build();
    }
}
